home *** CD-ROM | disk | FTP | other *** search
/ C# & Game Programming - A…er's Guide (2nd Edition) / Buono 2nd Ed.iso / Chapter3 / AsteroidMiner / AsteroidMiner GDI+ / AM Arcade.cs next >
Text File  |  2003-04-28  |  20KB  |  605 lines

  1. /* AsteroidMiner v2.0 - by Salvatore A. Buono */
  2. using System;
  3. using System.Drawing;
  4. using System.Windows.Forms;
  5. using System.IO; // for file read/write
  6.  
  7. using GameClasses; // Our common utility class
  8.  
  9. namespace Games {
  10.   public class AsteroidMiner : System.Windows.Forms.Form {
  11.     // Constants
  12.     const int SCALE = 25;
  13.     const string MEDIA_ROOT = @".\media\";
  14.     const int TIMER_BASE = 40;    // in millis.
  15.     const int MAX_STARS = 20;     // # of stars
  16.     const int MAX_ASTEROIDS = 4;  // # of asteroids (if on)
  17.     const int MAX_MISSILES = 2;   // # of missiles/ship
  18.     const int MISSILE_LIFE = 50;  // 40 ticks
  19.     const int THRUSTER_LIFE = 10; // 10 ticks
  20.     const int ASTEROID_SPEED = 10;// how much they move
  21.     const int GRAVITY_SPEED = 75; // how often gravity affects you
  22.     const int GRAVITY_EFFECT = 5; // how much it effects you
  23.     const int DEFAULT_SPEED = 5;  // default game speed
  24.     const int NUM_LIVES = 5;      // determines when the game is over
  25.     const int SCORE_ASTEROID = 10; // 10 points for hitting an asteroid
  26.  
  27.     // Image buffer to prevent screen-flicker
  28.     Bitmap offScreenBuffer;
  29.  
  30.     // Member fields -- UI elements
  31.     private System.Windows.Forms.MainMenu mainMenu1;
  32.     private System.Windows.Forms.MenuItem menuItem1;
  33.  
  34.     // Member fields
  35.     GameState gameState;  // Holds state information
  36.              Shapes player; // replaces jgp version with drawing
  37.  
  38.     AnimatedImage[] playerMissiles;
  39.     Shapes[] asteroids;
  40.  
  41.     Point[] stars;  // background star field
  42.  
  43.     GameTimer timer;    // Event timer to control all animation
  44.     TimedEvent p1Event; // The player event, for easy reference
  45.  
  46.     int HighScore = 0;        // Game's top score loads from file
  47.     int p1LastDirection = 0;
  48.  
  49.     // Main entry-point function
  50.     [STAThread] static void Main() {
  51.       Application.Run(new AsteroidMiner());
  52.     }
  53.  
  54.     #region Set-up & Initialization
  55.     // Default constructor
  56.     public AsteroidMiner() {
  57.       InitializeComponent();
  58.  
  59.       gameState = new GameState();
  60.  
  61.       // Set up timer & timed events
  62.       timer = new GameTimer(TIMER_BASE);
  63.       TimedEvent newEvent;
  64.  
  65.       // Initialize the two player objects
  66.        player = new Shapes(0);
  67.       player.constraintBox = this.ClientRectangle;
  68.  
  69.       // Load images into PlayerImageArrays
  70.       player.RescaleImage(SCALE, SCALE);
  71.  
  72.       // player thruster event
  73.       newEvent = new TimedEvent(THRUSTER_LIFE, 
  74.         new TickHandler(PlayerThrust));
  75.       newEvent.payload = player;
  76.       timer.addEvent(newEvent, 1, "player");
  77.       p1Event = newEvent;
  78.  
  79.  
  80.       // Initialize the background stars
  81.       Random rnd = new Random();
  82.       stars = new Point[MAX_STARS];
  83.       for (int i = 0; i < MAX_STARS; i++)
  84.         stars[i] = new Point(rnd.Next(ClientSize.Width), rnd.Next(ClientSize.Height));
  85.  
  86.  
  87.       // Initialize player's missiles
  88.      playerMissiles = new AnimatedImage[MAX_MISSILES];
  89.       for (int i = 0; i < MAX_MISSILES; i++) {
  90.         playerMissiles[i] = new AnimatedImage(0, 0);
  91.         playerMissiles[i].owner = player;
  92.         playerMissiles[i].constraintBox = this.ClientRectangle;
  93.         playerMissiles[i].RescaleImage(SCALE / 5, SCALE / 5);
  94.  
  95.         // Set up timer & events
  96.         newEvent = new TimedEvent(MISSILE_LIFE, 
  97.           new TickHandler(MoveMissile));
  98.         newEvent.payload = playerMissiles[i];
  99.         timer.addEvent(newEvent, 1, "playerMissile" + i);
  100.       }
  101.  
  102.       // Initialize the asteroids
  103.       asteroids = new Shapes[MAX_ASTEROIDS];
  104.       for (int i = 0; i < MAX_ASTEROIDS; i++) {
  105.         // Note, there are only 4 image positions so strange
  106.         // things happen when MAX_ASTEROIDS > 4
  107.         asteroids[i] = new Shapes((i % 2 == 1) ? 3 * SCALE :
  108.           20 * SCALE, (i < 2 ? 3 * SCALE : 12 * SCALE));
  109.         asteroids[i].direction = rnd.Next(1, 8);
  110.         asteroids[i].constraintBox = this.ClientRectangle;
  111.         asteroids[i].RescaleImage(2 * SCALE, 2 * SCALE);
  112.  
  113.         newEvent = new TimedEvent(new TickHandler(MoveAsteroids));
  114.         newEvent.payload = asteroids[i];
  115.         timer.addEvent(newEvent, ASTEROID_SPEED, "asteroid" + i);
  116.         newEvent.isActive = true;
  117.       }
  118.  
  119.       newEvent = new TimedEvent(new TickHandler(Gravity));
  120.       newEvent.isActive = true;
  121.       timer.addEvent(newEvent, GRAVITY_SPEED, "");
  122.  
  123.       // Call Invalidate() every tick
  124.       newEvent = new TimedEvent(new TickHandler(ScreenRefresh));
  125.       timer.addEvent(newEvent, 1, "invalidate");
  126.       newEvent.isActive = true;
  127.  
  128.  
  129.       
  130.     }
  131.  
  132.     // (re) Sets positional data, scores, and other game data
  133.     public void Setup() {
  134.       gameState.currentState = GameState.State.Started;
  135.       gameState.currentSpeed = DEFAULT_SPEED;
  136.  
  137.       player.imagePosX = 12 * SCALE - (player.imageWidth >> 1);
  138.       player.imagePosY = 8 * SCALE;
  139.  
  140.                     if (player.score > HighScore)
  141.                         SaveHighScore(player.score);
  142.       HighScore = LoadHighScore();
  143.       if(HighScore < player.score)
  144.        {HighScore = player.score;}
  145.       player.score = 0;
  146.  
  147.       
  148.       player.deaths = 0;
  149.       player.isActive = true;
  150.       player.direction = AnimatedImage.NORTH;
  151.  
  152.       for (int i = 0; i < MAX_MISSILES; i++) {
  153.         playerMissiles[i].isActive = false;
  154.       }
  155.  
  156.       for (int i = 0; i < MAX_ASTEROIDS; i++) {
  157.         asteroids[i].RescaleImage(2 * SCALE, 2 * SCALE);
  158.         asteroids[i].isActive = true;
  159.       }
  160.  
  161.       timer.Start();
  162.     }
  163.  
  164.     // Form's initialize method - we initialize all form related items
  165.     private void InitializeComponent() {
  166.                     this.mainMenu1 = new System.Windows.Forms.MainMenu();
  167.                     this.menuItem1 = new System.Windows.Forms.MenuItem();
  168.                     // 
  169.                     // mainMenu1
  170.                     // 
  171.                     this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
  172.                                                                                                                                                                                                                                                                                                                 this.menuItem1});
  173.                     // 
  174.                     // menuItem1
  175.                     // 
  176.                     this.menuItem1.Index = 0;
  177.                     this.menuItem1.Text = "Asteroid Miner";
  178.                     this.menuItem1.Click += new System.EventHandler(this.SinglePlayer_Click);
  179.                     // 
  180.                     // AsteroidMiner
  181.                     // 
  182.                     this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
  183.                     this.BackColor = System.Drawing.Color.Black;
  184.                     this.ClientSize = new System.Drawing.Size(600, 400);
  185.                     this.Menu = this.mainMenu1;
  186.                     this.Name = "AsteroidMiner";
  187.                     this.Text = "Asteroid Miner";
  188.                     this.Load += new System.EventHandler(this.AsteroidMiner_Load);
  189.  
  190.                 }
  191.     #endregion
  192.  
  193.     #region Game-play functions
  194.     // Start up the player's thrusters
  195.     public void EngageThrusters(Player player) {
  196.       if (player.isActive) {
  197.         if (player == player)
  198.           p1Event.Reset(); // reset the timer from now
  199.  
  200.         Utils.PlaySound(MEDIA_ROOT + "Thrust.wav");
  201.       }
  202.  
  203.       return;
  204.     }
  205.  
  206.     // Fire a missile (if any available) from the given player
  207.     public void FireMissile(Player player, AnimatedImage[] missiles) {
  208.       if (!player.isActive)
  209.         return;
  210.  
  211.       for (int i = 0; i < MAX_MISSILES; i++) {
  212.         // shoot the first missile that's not already out there!
  213.         if (!missiles[i].isActive) {
  214.           // set missiles properties so that it will get 
  215.           // properly animated
  216.           missiles[i].isActive = true;
  217.           missiles[i].direction = player.direction;
  218.           missiles[i].imagePosX = player.imagePosX;
  219.           missiles[i].imagePosY = player.imagePosY;
  220.  
  221.           TimedEvent missileEvent = timer.getEvent
  222.             ("playerMissile" + i);
  223.           missileEvent.Reset();
  224.  
  225.           Utils.PlaySound(MEDIA_ROOT + "Fire.wav");
  226.           break;
  227.         }
  228.       }
  229.     }
  230.  
  231.     public void ExertGravitationalPull(Player player) {
  232.       if (player.imagePosX != ClientSize.Width >> 1)
  233.         player.imagePosX += (player.imagePosX > 
  234.           (ClientSize.Width >> 1)) ? -GRAVITY_EFFECT : GRAVITY_EFFECT;
  235.       if (player.imagePosY != ClientSize.Height >> 1)
  236.         player.imagePosY += (player.imagePosY > 
  237.           (ClientSize.Height >> 1)) ? -GRAVITY_EFFECT : GRAVITY_EFFECT;
  238.     }
  239.     #endregion
  240.  
  241.     #region Collision Detection Routines
  242.     // Ships can run into the asteroids and missiles.
  243.     // We'll assume that the missiles will detect their collisions
  244.     // so we only have to worry about the others.
  245.     public bool CheckShipCollision(Player player) {      
  246.       Player opponent = player;
  247.  
  248.       // Asteroid collision?
  249.       for (int i = 0; i < MAX_ASTEROIDS; i++) {
  250.         if (asteroids[i].isActive) {
  251.           if (asteroids[i].Intersects(player)) {
  252.             ShipCollided(player);
  253.             return true;
  254.           }
  255.         }
  256.       }
  257.  
  258.       return false;
  259.     }
  260.  
  261.     // Asteroids can run into ships & missiles. We'll assume that 
  262.     // missiles will detect their collisions themselves since they 
  263.     // update frequently.
  264.     public bool CheckAsteroidCollision() {
  265.       for (int i = 0; i < MAX_ASTEROIDS; i++) {
  266.         if (asteroids[i].isActive) {
  267.           if (asteroids[i].Intersects(player)) {
  268.             ShipCollided(player);
  269.             return true;
  270.           }
  271.         }
  272.       }
  273.  
  274.       return false;
  275.     }
  276.  
  277.     // Missiles can only run into asteroids.
  278.     public bool CheckMissileCollision(AnimatedImage missile) {
  279.       if (missile.isActive) {
  280.         for (int i = 0; i < MAX_ASTEROIDS; i++) {
  281.           if (missile.Intersects(asteroids[i])) {
  282.             AsteroidCollided(asteroids[i]);
  283.             missile.isActive = false;
  284.             Utils.PlaySound(MEDIA_ROOT + "Hit.wav");
  285.             return true;
  286.           }
  287.         }
  288.       }
  289.  
  290.       return false;  // Must not have hit anything!
  291.     }
  292.  
  293.     // The ship ran into something! Add to the death count and
  294.     // end the game if we're out of lives.
  295.     public void ShipCollided(Player victim) {
  296.       if (!victim.isActive)
  297.         return; // He's already dead!
  298.  
  299.       victim.isActive = false;
  300.       victim.direction = 0; // index of our explosion image
  301.       Utils.PlaySound(MEDIA_ROOT + "Hit.wav");
  302.  
  303.       victim.deaths++;
  304.  
  305.       if(victim.deaths >= NUM_LIVES) {
  306.         gameState.currentState = GameState.State.Stopped;
  307.  
  308.         if (victim.score > HighScore) {
  309.           SaveHighScore(victim.score);
  310.         }
  311.       }
  312.     }
  313.  
  314.     // The asteroid was hit! Cycle through its different scaled
  315.     // display & increment the player's score.
  316.     public void AsteroidCollided(AnimatedImage victim) {
  317.       int scaleFactor = (victim.imageWidth <= (SCALE / 2)) ? 
  318.         SCALE * 2 : victim.imageWidth / 2;
  319.       victim.RescaleImage(scaleFactor, scaleFactor);
  320.  
  321.       player.score += SCORE_ASTEROID;
  322.     }
  323.     #endregion
  324.  
  325.             #region OnPaint
  326.             protected override void OnPaint(PaintEventArgs e) 
  327.             {
  328.                 Graphics gOffScreen;     // Graphics context for offscreen buffer
  329.                 Graphics g = e.Graphics; // Screen's graphics object
  330.  
  331.                 // If the size of the window has changed, we need a new buffer
  332.                 if (offScreenBuffer == null || 
  333.                     offScreenBuffer.Width != this.ClientSize.Width ||
  334.                     offScreenBuffer.Height != this.ClientSize.Height) 
  335.                 {
  336.                     if (ClientSize.Width == 0 || ClientSize.Height == 0)
  337.                         return;
  338.                     offScreenBuffer = new Bitmap(this.ClientSize.Width,
  339.                         this.ClientSize.Height);
  340.                 }
  341.  
  342.                 // We paint just as if it were the screen. The function need not
  343.                 // know it is being buffered.
  344.                 gOffScreen = Graphics.FromImage(offScreenBuffer);
  345.                 DoPaint(gOffScreen);   // Call normal paint method
  346.                 gOffScreen.Dispose();  // Free up resources right away
  347.  
  348.                 // We draw our buffered image onto the screen in one operation.
  349.                 // This could be improved further still by using clip regions.
  350.                 g.DrawImage(offScreenBuffer, 0, 0);
  351.             }
  352.  
  353.             public void DoPaint(Graphics g) 
  354.             {
  355.                 // Redraw the background
  356.                 g.FillRectangle(Brushes.Black, 0, 0,
  357.                     this.Size.Width, this.Size.Height);
  358.  
  359.                 // Write start-up text if game is stopped
  360.                 if (gameState.currentState == GameState.State.Stopped) 
  361.                 {
  362.                     Brush YellowBrush = Brushes.Yellow;
  363.                     Font LargeAlgerianFont = new Font("Algerian", 24);
  364.                     Font MidAlgerianFont = new Font("Algerian", 18);
  365.  
  366.                     g.DrawString("C# and Game Programming", LargeAlgerianFont, YellowBrush, 2 * SCALE, SCALE);
  367.                     g.DrawString("A Beginner's Guide", LargeAlgerianFont, YellowBrush, 9 * SCALE, 5 * SCALE);
  368.                     g.DrawString("By Salvatore A. Buono", MidAlgerianFont, YellowBrush, 2 * SCALE, 14 * SCALE);
  369.                 }    
  370.                 else 
  371.                 {    Font Normal = new Font("Time New Roman", 14, FontStyle.Bold);
  372.                     // Draw the scores
  373.                     if(player.score < HighScore)
  374.                     {g.DrawString(HighScore.ToString(), Normal, Brushes.Blue, 
  375.                             4 * SCALE, SCALE);}
  376.                     else 
  377.                     {    g.DrawString(player.score.ToString(), Normal, Brushes.Blue, 
  378.                         4 * SCALE, SCALE);}
  379.  
  380.                     g.DrawString(player.score.ToString(), Normal,  Brushes.Blue, 
  381.                         16 * SCALE, SCALE);
  382.  
  383.                     // Draw the stars
  384.                     for(int i = 0; i < MAX_STARS; i++)
  385.                         g.FillEllipse(Brushes.OrangeRed, stars[i].X, stars[i].Y, 5, 5);
  386.  
  387.                     // Draw player 1 & its appropriate exhaust
  388.                     if(player.isActive == true)
  389.                     {
  390.                         g.DrawPie(Pens.Blue, player.imagePosX, player.imagePosY, 
  391.                             SCALE, SCALE, player.startAngle(), 50);
  392.                         if (p1Event.isActive) 
  393.                         {
  394.                             player.Exhuast(g, Brushes.Aqua);
  395.                         }
  396.                     }
  397.  
  398.                     // Draw missiles
  399.                     for (int i = 0; i < MAX_MISSILES; i ++) 
  400.                     {
  401.                         if (playerMissiles[i].isActive)
  402.                             g.FillEllipse(Brushes.Blue, playerMissiles[i].imagePosX,
  403.                                 playerMissiles[i].imagePosY, 5, 5);       
  404.                     }
  405.  
  406.                     // Draw asteroids
  407.                     asteroids[0].Asteroid(g, Brushes.Red);
  408.                     asteroids[1].Asteroid(g, Brushes.Blue);
  409.                     asteroids[2].Asteroid(g, Brushes.Orange);
  410.                     asteroids[3].Asteroid(g, Brushes.Yellow);
  411.                 }
  412.             }
  413.     #endregion
  414.  
  415.     #region OnKey
  416.     protected override void OnKeyDown(KeyEventArgs e) {
  417.       if (gameState.currentState != GameState.State.Started) 
  418.         return;
  419.  
  420.       // Player controls
  421.       switch(e.KeyCode) {
  422.         case Keys.Up:
  423.           p1LastDirection = player.direction;
  424.           EngageThrusters(player);
  425.           break;
  426.         case Keys.Left:
  427.           if (player.isActive)
  428.             player.RotateDirection(1);
  429.           break;
  430.         case Keys.Right:
  431.           if (player.isActive)
  432.             player.RotateDirection(-1);
  433.           break;
  434.         case Keys.Down:
  435.           if (!player.isActive)
  436.             player.isActive = true;
  437.           player.RandomizePosition();
  438.           break;
  439.         case Keys.NumPad0:
  440.         case Keys.Space:
  441.           FireMissile(player, playerMissiles);
  442.           break;
  443.  
  444.         // Exit game
  445.         case Keys.Escape:
  446.           ExitApplication();
  447.           break;
  448.       }
  449.       base.OnKeyDown(e);
  450.     }
  451.     #endregion    
  452.  
  453.     #region TimedEvent Tick Handlers
  454.     // Implements the player's thruster push. Called repetitively until
  455.     // the timer counts down to 0.
  456.     public void PlayerThrust(TimedEvent e, Object obj) {
  457.       if (gameState.currentState == GameState.State.Stopped) {
  458.         e.isActive = false;
  459.         return;
  460.       }
  461.  
  462.       Player player = (Player) obj;
  463.       int tempDir = player.direction;
  464.  
  465.       player.direction = p1LastDirection;
  466.       player.Animate();
  467.       player.Animate();
  468.       player.direction = tempDir;
  469.       player.WrapInBox();
  470.  
  471.       CheckShipCollision(player);
  472.     }
  473.  
  474.     // Implements the missile's movement. Called automatically until
  475.     // the timer counts down to 0.
  476.     public void MoveMissile(TimedEvent e, Object obj) {
  477.       if (gameState.currentState == GameState.State.Stopped) {
  478.         e.isActive = false;
  479.         return;
  480.       }
  481.  
  482.       // Get a proper missile object from the event
  483.       AnimatedImage missile = (AnimatedImage) obj;
  484.       for(int i = 0; i < gameState.currentSpeed; i++) {
  485.         missile.Animate();
  486.         missile.WrapInBox();
  487.  
  488.         // See if we hit anything!
  489.         CheckMissileCollision(missile);
  490.       }
  491.  
  492.       // disable the missile so it can be fired again
  493.       if (e.tickCounter <= 1)
  494.         missile.isActive = false;
  495.     }
  496.  
  497.     // Implements the asteroid's random movement. This is called
  498.     // continously throughout the game.
  499.     public void MoveAsteroids(TimedEvent e, Object obj) {
  500.       if (gameState.currentState == GameState.State.Stopped) {
  501.         e.isActive = false;
  502.         return;
  503.       }
  504.  
  505.       Random rnd = new Random();
  506.       for(int i = 0; i < MAX_ASTEROIDS; i++) {
  507.         if (rnd.Next(10) > 7) // Change the direction ~70% of the time
  508.           asteroids[i].direction = rnd.Next(1, 8);
  509.         asteroids[i].Animate();
  510.         asteroids[i].WrapInBox();
  511.         CheckAsteroidCollision();
  512.       }
  513.     }
  514.  
  515.     // Implements the gravity (from an unseen field -- that only 
  516.     // affects ships -- located at the center of the screen).
  517.     public void Gravity(TimedEvent e, Object obj) {
  518.       if (gameState.currentState == GameState.State.Stopped) {
  519.         e.isActive = false;
  520.         return;
  521.       }
  522.  
  523.       if (player.isActive){ // Doesn't pull "dead" ships
  524.         ExertGravitationalPull(player);
  525.         CheckShipCollision(player);
  526.       }
  527.  
  528.       Utils.PlaySound(MEDIA_ROOT + "Gravity.wav");
  529.     }
  530.  
  531.     // Regular refresh function. The screen will redraw every tick.
  532.     public void ScreenRefresh(TimedEvent e, Object obj) {Invalidate();}
  533.     #endregion
  534.  
  535.     #region Menu Click Handler
  536.     private void SinglePlayer_Click(object sender, System.EventArgs e) {
  537.       gameState.currentMode = GameState.Mode.SinglePlayer;
  538.       Setup();
  539.     }
  540.     #endregion
  541.  
  542.     #region File Read/Write
  543.     // Utility function to read the high score from a file
  544.     public int LoadHighScore() {
  545.       StreamReader file;
  546.       string strTemp = "";
  547.       int score = 0;
  548.  
  549.       try {
  550.         file = new StreamReader(@".\HighScore.txt");
  551.         for (int i = 0; i < 4; i++)
  552.           strTemp = file.ReadLine();
  553.         score = int.Parse(strTemp.Substring(0, strTemp.IndexOf("\t")));
  554.         file.Close();
  555.       } catch (Exception e) {Console.WriteLine(e);}
  556.  
  557.       return score;
  558.     }
  559.  
  560.     // Utility function to write the high score from a file
  561.     public void SaveHighScore(int score) {
  562.       StreamWriter file;
  563.  
  564.       try {
  565.         file = new StreamWriter(@".\HighScore.txt");
  566.         file.WriteLine("Asteroid Miner High Scores");
  567.         file.WriteLine("--------------------------");
  568.         file.WriteLine();
  569.         file.WriteLine(score + "\t" + DateTime.Now);
  570.         file.Flush();
  571.         file.Close();
  572.       } catch (Exception e) {Console.WriteLine(e);}
  573.     }
  574.     #endregion
  575.  
  576.     // Clean exit function
  577.     public void ExitApplication() {
  578.       if (MessageBox.Show ("Are you sure you want to quit?", "End Game", 
  579.           MessageBoxButtons.YesNo) == DialogResult.Yes) 
  580.         Application.Exit(); 
  581.     }
  582.  
  583.     // The default Invalidate clears the entire screen creating a very 
  584.     // undesireable flicker. This override allows the paint method to
  585.     // control it's redraw.
  586.     public new void Invalidate() {
  587.       OnPaint(new PaintEventArgs(Graphics.FromHwnd(this.Handle), 
  588.         new Rectangle(new Point(0, 0), this.Size)));
  589.     }
  590.  
  591.     // The system occasionally also calls OnPaintBackground which will
  592.     // mean the screen will be cleared, painted with the background 
  593.     // color, then repainted. This is another cause of flicker, so we
  594.     // just override it and tell it not to do anything!
  595.     protected override void OnPaintBackground(PaintEventArgs e) {
  596.       // Do nothing!
  597.     }
  598.  
  599.             private void AsteroidMiner_Load(object sender, System.EventArgs e)
  600.             {
  601.             
  602.             }
  603.   }
  604. }
  605.